package com.chris.module.webclient.core;

import com.chris.module.webclient.dto.ConfigDTO;
import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.codec.ClientCodecConfigurer;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.netty.transport.ProxyProvider;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.function.Consumer;

/**
 * 异步http RPC
 * 2023-7-2
 * 参考：
 * http://www.manongjc.com/detail/22-psrwoqcoioslddk.html
 * https://docs.spring.io/spring-framework/reference/web/webflux-webclient/client-builder.html
 */
public class WebClientUnit {

    //region 构造函数 & 参数设置
    /**
     * WebClient
     */
    private WebClient webClient;

    public WebClient getWebClient() {
        return webClient;
    }

    /**
     * 链接池
     */
    //private ReactorResourceFactory factory;

    /**
     * 构造函数(传入参数模式)
     *
     * @param config
     */
    public WebClientUnit(ConfigDTO config)  {
        intData(config);
    }

    /**
     * 构造函数 （默认方式）
     */
    public WebClientUnit()
    {
        ConfigDTO config = new ConfigDTO();
        config.setProxy(false);
        config.setConnectTimeout(30);
        config.setReadTimeout(30);
        config.setWriteTimeout(30);
        intData(config);
    }


    /**
     * 构造函数 (默认代理模式)
     * @param host
     * @param port
     */
    public WebClientUnit(String host ,int port)
    {
        ConfigDTO config = new ConfigDTO();
        config.setProxy(true);
        config.setHost(host);
        config.setPort(port);
        config.setConnectTimeout(30);
        config.setReadTimeout(30);
        config.setWriteTimeout(30);
        intData(config);
    }

    /**
     * 初始化
     * @param config
     */
    private void intData(ConfigDTO config)
    {
        /**
         * 连接池
         */
        //ClientHttpConnector connector = new ReactorClientHttpConnector();
        ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient(config));
        // ClientHttpConnector connector = new ReactorClientHttpConnector(resourceFactory(), mapper());




        /**
         *  读取缓冲
         */
        Consumer<ClientCodecConfigurer> configurer = new Consumer<ClientCodecConfigurer>() {
            @Override
            public void accept(ClientCodecConfigurer clientCodecConfigurer) {
                clientCodecConfigurer.defaultCodecs().maxInMemorySize(Integer.MAX_VALUE);
            }
        };

        this.webClient = WebClient.builder().clientConnector(connector).codecs(configurer).build();
    }

    /**
     * 工厂(私有非全局)
     *
     * @return

    private ReactorResourceFactory resourceFactory() {
        ReactorResourceFactory factory = new ReactorResourceFactory();
        factory.setUseGlobalResources(false);
        factory.setConnectionProvider(ConnectionProvider.create("httpClient", 500, true));
        factory.setLoopResources(LoopResources.create("httpClient", 4, true));
        return factory;
    }
 */
    /**
     * HttpClient 设置
     */
    private HttpClient httpClient(ConfigDTO config) {
        //-------------设置超时------------
        HttpClient httpClient= HttpClient.create()
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getConnectTimeout() * 1000)
                .doOnConnected(
                        c -> c.addHandlerLast(new ReadTimeoutHandler(config.getReadTimeout()))
                                .addHandlerLast(new WriteTimeoutHandler(config.getWriteTimeout()))
                );
        //----------设置代理------------
        if(config.isProxy()) {
            httpClient.proxy(proxy -> proxy
                    .type(config.getType().equals("http")?ProxyProvider.Proxy.HTTP:ProxyProvider.Proxy.SOCKS5)
                    .host(config.getHost())
                    .port(config.getPort()));
        }
        //--------------------------------
        return  httpClient;
    }
    //endregion

    /**
     * mapper
     *
     * @return

    private Function<HttpClient, HttpClient> mapper(ConfigDTO config) {
        return new Function<HttpClient, HttpClient>() {
            @Override
            public HttpClient apply(HttpClient httpClient) {
                return httpClient(config);
            }
        };
    }
*/

    //region get & getList 方法
    public Mono<String> get(String url, HttpHeaders headers, MultiValueMap<String, String> cookies) {
        return get(url, headers, cookies, String.class);
    }

    public <T> Mono<T> get(String url, HttpHeaders headers, MultiValueMap<String, String> cookies, Class T) {

        @SuppressWarnings("unchecked")
        Mono<T> mono = webClient
                //.method(HttpMethod.GET) // GET 请求
                .get()
                .uri(url)  // 请求路径
                .headers(consumerHeaders(headers))//headers 设置
                .cookies(consumerCookies(cookies))//cokkies 设置
                .retrieve() // 获取响应体
                .bodyToMono(T); //响应数据类型转换
        return mono;
    }

    public <T> Mono<List<T>> getList(String url, HttpHeaders headers, MultiValueMap<String, String> cookies, Class T) {
        @SuppressWarnings("unchecked")
        Flux<T> flux = webClient
                //.method(HttpMethod.GET) // GET 请求
                .get()
                .uri(url)  // 请求路径
                .headers(consumerHeaders(headers))//headers 设置
                .cookies(consumerCookies(cookies))//cokkies 设置
                .retrieve() // 获取响应体
                .bodyToFlux(T); //响应数据类型转换
        return flux.collectList();
    }
    //endregion

    //region postForm & postFormList 方法
    public Mono<String> postForm(String url, MultiValueMap<String, String> params, HttpHeaders headers, MultiValueMap<String, String> cookies) {
        return postForm(url, params, headers, cookies, String.class);
    }

    public <T> Mono<T> postForm(String url, MultiValueMap<String, String> params, HttpHeaders headers, MultiValueMap<String, String> cookies, Class T) {
        @SuppressWarnings("unchecked")
        Mono<T> mono = webClient
                //.method(HttpMethod.POST)// POST 请求
                .post()
                .uri(url)// 请求路径
                .acceptCharset(StandardCharsets.UTF_8)//提交字符集
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)//提交文本
                .headers(consumerHeaders(headers))//headers 设置
                .cookies(consumerCookies(cookies))//cokkies 设置
                .bodyValue(params)//提交参数
                .retrieve()// 获取响应体
                .bodyToMono(T);//响应数据类型转换
        return mono;
    }


    public <T> Mono<List<T>> postFormList(String url, MultiValueMap<String, String> params, HttpHeaders headers, MultiValueMap<String, String> cookies, Class T) {
        @SuppressWarnings("unchecked")
        Flux<T> flux = webClient
                //.method(HttpMethod.POST)// POST 请求
                .post()
                .uri(url)// 请求路径
                .acceptCharset(Charset.defaultCharset())//提交字符集
                .contentType(MediaType.TEXT_PLAIN)//提交文本
                .headers(consumerHeaders(headers))//headers 设置
                .cookies(consumerCookies(cookies))//cokkies 设置
                .bodyValue(params)//提交参数
                .retrieve()// 获取响应体
                .bodyToFlux(T);//响应数据类型转换
        return flux.collectList();
    }

    //endregion

    //region postObject & postObjectList 方法
    public Mono<String> postObject(String url, Object ob, HttpHeaders headers, MultiValueMap<String, String> cookies) {
        return postObject(url, ob, headers, cookies, String.class);
    }

    public <T> Mono<T> postObject(String url, Object ob, HttpHeaders headers, MultiValueMap<String, String> cookies, Class T) {
        @SuppressWarnings("unchecked")
        Mono<T> mono = webClient
                //.method(HttpMethod.POST)
                .post()
                .uri(url)
                .acceptCharset(StandardCharsets.UTF_8)//提交字符集
                //.contentType(MediaType.ALL)//提交文本
                .headers(consumerHeaders(headers))//headers 设置
                .cookies(consumerCookies(cookies))//cokkies 设置
                .bodyValue(ob)
                .retrieve()
                .bodyToMono(T);//响应数据类型转换
        return mono;
    }

    public <T> Mono<List<T>> postObjectList(String url, Object ob, HttpHeaders headers, MultiValueMap<String, String> cookies, Class T) {
        @SuppressWarnings("unchecked")
        Flux<T> flux = webClient
                //.method(HttpMethod.POST)
                .post()
                .uri(url)
                .acceptCharset(StandardCharsets.UTF_8)//提交字符集
                //.contentType(MediaType.APPLICATION_JSON)//提交文本
                .headers(consumerHeaders(headers))//headers 设置
                .cookies(consumerCookies(cookies))//cokkies 设置
                .bodyValue(ob)
                .retrieve()
                .bodyToFlux(T);//响应数据类型转换
        return flux.collectList();
    }
    //endregion

    //region common 方法

    /**
     * 构造 Consumer HttpHeaders
     *
     * @param headers
     * @return
     */
    private Consumer<HttpHeaders> consumerHeaders(HttpHeaders headers) {
        Consumer<HttpHeaders> h = httpHeaders -> {
            if (headers != null)
                httpHeaders.putAll(headers);
        };
        return h;
    }

    /**
     * 构造 Consumer cookies
     *
     * @param cookies
     * @return
     */
    private Consumer<MultiValueMap<String, String>> consumerCookies(MultiValueMap<String, String> cookies) {
        Consumer<MultiValueMap<String, String>> c = stringStringMultiValueMap -> {
            if (cookies != null)
                stringStringMultiValueMap.putAll(cookies);
        };
        return c;
    }
    //endregion


}
